package de.gwtprime.server.servlets;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringEscapeUtils;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

import de.gwtprime.server.model.Article;
import de.gwtprime.server.model.ArticleSection;
import de.gwtprime.server.util.AppUtils;
import de.gwtprime.shared.util.AppUtilsShared;


/**
 * This servlet checks if the viewer is the google bot (google bot replaces "!#" with "_escaped_fragment_=").
 * If it is the google bot -> use the headless browser (html unit) to parse the site and serve the html-output to the bot.
 * -- Currently not working locally -- 
 * @author jan
 * To call a site like 
 * 
 * http://127.0.0.1:8888/xxx.html?gwt.codesvr=127.0.0.1:9997#!yyy:zzz 
 * 
 * like the bot, enter the url:
 * 
 * http://127.0.0.1:8888/xxx.html?gwt.codesvr=127.0.0.1:9997_escaped_fragment_=yyy:zzz
 * 
 * e.g.: http://127.0.0.1:8888/GWTPrime.html?gwt.codesvr=127.0.0.1:9997_escaped_fragment_=Article:1
 *
 * On Prod: http://www.xxx.appspot.com/?_escaped_fragment_=Article:1
 * 
 * On Prod: http://www.xxx.appspot.com/?_escaped_fragment_= for the welcome page
 * To test on google webmaster tools http://www.xxx.appspot.com!# (does not work like the actual google crawler
 * 
 * Test with fetch as google -> the test works not correctly for the base url (meta tag fragment)
 */
public class CrawlServlet implements Filter{
	
	/**
	 * Logger
	 */
	private static Logger logger = Logger.getLogger(CrawlServlet.class.getName());

	/**
	 * Give the browser time to execute the javascript.
	 */
	private static final long PUMP_TIME = 5000;

	/**
	 * Protocol
	 */
	private static final String PROTOCOL = "http";

	/**
	 * Headless browser to execute the javascript.
	 */
	private WebClient webClient;

	/**
	 * Close all windows if this servlet is destroyed.
	 */
	@Override
	public void destroy() {
		if (webClient != null) {
			webClient.closeAllWindows();
		}
	}

	/**
	 * Filter every request and check if it is the google bot.
	 */
	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		logger.info("Request: "+request);
		HttpServletRequest req = null;
		String queryString = null;

		String requestURI = null;
		if(request instanceof HttpServletRequest){
			req = (HttpServletRequest)request;

			// Get the parameters from the request
			queryString = req.getQueryString();
			requestURI = req.getRequestURI();
		}

		if(checkFacebookUrl(response, req, requestURI)){
			return;
		}

		// Check if it the google bot
		if ((queryString != null) && (queryString.contains("_escaped_fragment_"))) {

			serveHtmlToGoogleBot(response, req, queryString);

		}else{

			chain.doFilter(request, response);
		}
	}

	/**
	 * Check if facebook url is requested (e.g. "/fb/articleSection/73")
	 * If yes, write out some meta tags for the facebook crawler and
	 * add a javascript link to the correct url. This will forward any real user
	 * to the correct page.
	 * @param response
	 * @param req
	 * @param requestURI
	 * @return
	 */
	private boolean checkFacebookUrl(ServletResponse response,
			HttpServletRequest req, String requestURI) {
		if( requestURI != null){

			// needs three parameters e.g. /fb/articleSection/73
			// [1] -> fb for facebook
			// [2] -> entity name like articleSection
			// [3] -> ID of the entity
			
			try{

				String[] s = requestURI.split("/");
				if(s[1].equals("fb")){
					
					ArticleSection section = ArticleSection.findArticleSection(Long.parseLong(s[3]));
					if(section==null){
						response.getWriter().println("no valid id given");
						return true;
					}
					
					String image = null;
					String title = section.getTitle();
					String description = section.getText();
					Article article = section.getArticle();
					if(article!=null){
						image = article.getImage() != null ? AppUtils.getHostUrl()+"/img/"+article.getImage() : AppUtils.getHostUrl()+"/img/preview_placeholder.png";
					}
					
					wirteFacebookOutput(response, requestURI, image, title,	description, req);

					return true;
				}
			}catch(Exception e){
				logger.log(Level.SEVERE, "Parsing facebook url failed:", e);
			}
		}
		
		return false;
	}

	/**
	 * Required meta data for facebook are: title, description, image
	 * @param response
	 * @param requestURI
	 * @param image
	 * @param title
	 * @param description
	 * @throws IOException
	 */
	private void wirteFacebookOutput(ServletResponse response,
			String requestURI, String image, String title, String description, HttpServletRequest req)
			throws IOException {
		response.getWriter().println("<html>");
		response.getWriter().println("<head>");
		
		/**
		 * Facebook tags
		 */
		response.getWriter().println("<meta property='og:title' content='"+title+"'/>  ");
		response.getWriter().println("<meta property='og:description' content='"+StringEscapeUtils.escapeHtml4(description)+"'/>  ");
		response.getWriter().println("<meta property='og:image' content='"+image+"'/>  ");
		response.getWriter().println("<meta property='og:url' content='"+req.getRequestURL()+"'/>  ");
		
		/**
		 * Google tags
		 */
		response.getWriter().println("<link rel='canonical' href='"+req.getRequestURL()+"'/>");
		response.getWriter().println("<meta itemprop='name' content='"+title+"'/>  ");
		response.getWriter().println("<meta itemprop='description' content='"+StringEscapeUtils.escapeHtml4(description)+"'/>  ");
		response.getWriter().println("<meta itemprop='image' content='"+image+"'/>  ");

		response.getWriter().println("</head>");
		response.getWriter().println("<body>");
		response.getWriter().println(String.format("only for fb <script>window.location = '%s'</script>;", AppUtils.getHostUrl()+AppUtilsShared.convertToGWTToken(requestURI)));
		response.getWriter().println("</body>");
		response.getWriter().println("</html>");
	}

	/**
	 * Use the headless browser to generate the HTML from the javascript and serve it to the google bot.
	 * @param response
	 * @param req
	 * @param queryString
	 * @throws MalformedURLException
	 * @throws IOException
	 */
	private void serveHtmlToGoogleBot(ServletResponse response,
			HttpServletRequest req, String queryString)
					throws MalformedURLException, IOException {
		
		response.setContentType("text/html");
		response.setCharacterEncoding("UTF-8");
		response.getWriter().println("<!doctype html>");
		
		response.getWriter().println("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"de\" lang=\"de\">");
		response.getWriter().println("<head>");
		
		response.getWriter().println(String.format("<title>%s</title>", AppUtilsShared.appTitle));
		
		response.getWriter().println("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");
		response.getWriter().println(String.format("<meta name=\"description\" content=\"%s\">", AppUtilsShared.metaDescription));
		
		
		response.getWriter().println("</head>");
		
		response.getWriter().println("<body>");
		
		String entity = CrawlServletHelper.getEntityUrl(queryString);
		
		if("".equals(entity)){
		
			String result = CrawlServletHelper.getWelcomePageContentForGoogle();
			response.getWriter().println(result);
			return;
		}else if("Article".equals(entity)){
			
			String result = CrawlServletHelper.getArticleContentForGoogle(CrawlServletHelper.getIdFromUrl(queryString));
			response.getWriter().println(result);
			return;
			
		}else if("ArticleSection".equals(entity)){
			
			String result = CrawlServletHelper.getArticleSectionContentForGoogle(CrawlServletHelper.getIdFromUrl(queryString));
			response.getWriter().println(result);
			return;
			
		}else if("Question".equals(entity)){
			
			String result = CrawlServletHelper.getQuestionContentForGoogle(CrawlServletHelper.getIdFromUrl(queryString));
			response.getWriter().println(result);
			return;
			
		}
		
		
		
		response.setContentType("text/html");
		logger.info("Google bot is visiting the site");
		webClient = new WebClient(BrowserVersion.FIREFOX_3_6); // TODO switch to better version -> better version will be included in GWT 2.6

		String uri = req.getRequestURI();
		int port = req.getServerPort();
		String domain = req.getServerName();
		// rewrite the URL back to the original #! version
		// remember to unescape any %XX characters
		String url_with_hash_fragment = uri    + rewriteQueryString(queryString);


		URL url = new URL(PROTOCOL, domain, port, url_with_hash_fragment);
		logger.info("Parse this url for the google bot: "+url);

		// Open the url in the headless browser
		HtmlPage page = webClient.getPage(url);

		// important!  Give the headless browser enough time to execute JavaScript
		// The exact time to wait may depend on your application.
		
		//webClient.waitForBackgroundJavaScript(5000);
		webClient.getJavaScriptEngine().pumpEventLoop(PUMP_TIME);
		logger.info("did wait");

		response.getWriter().println(page.asXml());
	}


	/**
	 * Rewrite the "_escaped_fragment_" to the pretty url with "#!"
	 * @param urlWithEscapedFragment The escaped url
	 * @return The unescaped url.
	 */
	public String rewriteQueryString(String urlWithEscapedFragment) {
		try {
			String decoded = URLDecoder.decode(urlWithEscapedFragment,"UTF-8");

			//this helps run on development mode
			String gwt = decoded.replace("gwt", "?gwt");

			
			String unescapedAmp = gwt.replace("&_escaped_fragment_=", "#!");
			unescapedAmp = gwt.replace("&_escaped_fragment_", "#!");
			unescapedAmp = gwt.replace("_escaped_fragment_=", "#!");
			String result = unescapedAmp.replace("_escaped_fragment_", "#!");

			return result;

		} catch (UnsupportedEncodingException e) {
			logger.log(Level.SEVERE, "Rewriting escaped google bot url failed:", e);
			return "";
		}
	}


	/**
	 * Initialize the filter.
	 */
	@Override
	public void init(FilterConfig config) throws ServletException {
		
	}
}
